//
//  OYUploader.m
//  ObjectiveYouTube
//
//  Created by Tristan O'Tierney on 1/11/08.
//  Copyright 2008 Tristan O'Tierney. All rights reserved.
//

#import <ObjectiveYouTube/ObjectiveYouTube.h>

#define DEBUG 0

static NSString *kOYNotAuthenticatedErrorDomain = @"OYUploaderNotAuthenticated";
const static int kOYNotAuthenticatedErrorCode = 200;
static NSString *kOYMissingRequiredFieldErrorDomain = @"OYUploaderMissingRequiredField";
const static int kOYMissingRequiredFieldErrorCode = 300;
static NSString *kOYFileDoesntExistErrorDomain = @"OYUploaderFileDoesntExist";
const static int kOYFileDoesntExistErrorCode = 400;
static NSString *kOYTimeOutErrorDomain = @"OYUploaderTimeOut";
const static int kOYTimeOutErrorCode = 500;
static NSString *kOYUploadInProgressErrorDomain = @"OYUploaderUploadInProgress";
const static int kOYUploadInProgressErrorCode = 600;
static NSString *kOYResponseIDNotFoundErrorDomain = @"OYResponseIDNotFound";
const static int kOYResponseIDNotFoundErrorCode = 700;
static NSString *kOYResponseErrorDomain = @"OYResponseError";
const static int kOYResponseErrorCode = 800;

static NSString *kOYBaseURL = @"http://uploads.gdata.youtube.com/feeds/api/users/%@/uploads";
static NSString *kOYAuthHeaderPrefix = @"GoogleLogin auth=";
static NSString *kOYDeveloperKeyPrefix = @"key=";
//static NSString *kOYCalendarFormat = @"%Y-%m-%d";
static NSString *kOYVideoContentType = @"video/quicktime";

const static float kOYTimeOutInterval = 60.0f;

@interface OYUploader (Private)
- (void)_delegateError:(NSString *)domain withCode:(int)code;
- (void)_delegateError:(NSError *)error;
- (void)_cleanupUpload;
@end

@implementation OYUploader
#pragma mark -
#pragma mark Initialization
#pragma mark -

- (id)initWithDelegate:(id)delegate authenticator:(OYAuthenticator *)authenticator {
	if (self = [super init]) {
		[self setAuthenticator:authenticator];
		_delegate = delegate;
		_uploadRequest = nil;
		_contextInfo = NULL;
	}
	
	return self;
}

- (void)dealloc {
	[self cancelUpload];

	_delegate = nil;	
	
	[_authenticator release];
	_authenticator = nil;
	
	_contextInfo = NULL;
	
	[super dealloc];
}

#pragma mark -
#pragma mark Public Methods
#pragma mark -

- (id)delegate {
	return _delegate;
}

- (void)setDelegate:(id)delegate {
	_delegate = delegate;
}

- (void)uploadFile:(NSString *)fileName
		 withTitle:(NSString *)title
	   description:(NSString *)description
		  category:(OYCategory *)category
			  tags:(NSArray *)tags
		   privacy:(OYPrivacyType)privacy
			  date:(NSDate *)date
	   contextInfo:(void *)info {
	
	if (!title || ![title length] ||
		!description || ![description length]) {
		return [self _delegateError:kOYMissingRequiredFieldErrorDomain withCode:kOYMissingRequiredFieldErrorCode];
	}
	
	if ([self isUploading]) {
		// We don't bother queuing because you can just create a new instance!
		return [self _delegateError:kOYUploadInProgressErrorDomain withCode:kOYUploadInProgressErrorCode];
	}
	
	// does the file we're uploading even exist?
	NSFileManager *manager = [NSFileManager defaultManager];
	if (![manager fileExistsAtPath:fileName]) {
		return [self _delegateError:kOYFileDoesntExistErrorDomain withCode:kOYFileDoesntExistErrorCode];
	}
	
	// are we logged in?
	if (!_authenticator || ![_authenticator isLoggedIn]) {
		return [self _delegateError:kOYNotAuthenticatedErrorDomain withCode:kOYNotAuthenticatedErrorCode];
	}
	
	_contextInfo = info;
	
	// build the post
	NSString *uploadURLString = [NSString stringWithFormat:kOYBaseURL, [_authenticator username]];
	NSURL *uploadURL = [NSURL URLWithString:uploadURLString];
	OYMultiPartForm *form = [[[OYMultiPartForm alloc] init] autorelease];
	NSData *formData;
	
	// now let's build our form
	NSXMLElement *root = [NSXMLNode elementWithName:@"entry"];
	NSXMLDocument *metaDataDoc = [[[NSXMLDocument alloc] initWithRootElement:root] autorelease];
	
	[root addAttribute:[NSXMLNode attributeWithName:@"xmlns" stringValue:@"http://www.w3.org/2005/Atom"]];
	[root addAttribute:[NSXMLNode attributeWithName:@"xmlns:media" stringValue:@"http://search.yahoo.com/mrss/"]];
	[root addAttribute:[NSXMLNode attributeWithName:@"xmlns:yt" stringValue:@"http://gdata.youtube.com/schemas/2007"]];
	
	NSXMLElement *mediaGroup = [NSXMLNode elementWithName:@"media:group"];
	[root addChild:mediaGroup];
	
	NSXMLElement *mediaTitle = [NSXMLNode elementWithName:@"media:title"];
	[mediaTitle setStringValue:title];
	[mediaTitle addAttribute:[NSXMLNode attributeWithName:@"type" stringValue:@"plain"]];
	[mediaGroup addChild:mediaTitle];
	
	NSXMLElement *mediaDescription = [NSXMLNode elementWithName:@"media:description"];
	[mediaDescription setStringValue:description];
	[mediaDescription addAttribute:[NSXMLNode attributeWithName:@"type" stringValue:@"plain"]];
	[mediaGroup addChild:mediaDescription];
	
	NSXMLElement *mediaCategory = [NSXMLNode elementWithName:@"media:category"];
	[mediaCategory setStringValue:[category term]];
	[mediaCategory addAttribute:[NSXMLNode attributeWithName:@"scheme" stringValue:kOYCategoryURL]];
	[mediaGroup addChild:mediaCategory];
	
	NSMutableArray *allTags = [NSMutableArray array];
	NSEnumerator *en = [tags objectEnumerator];
	NSString *tag = nil;
	
	while ((tag = [en nextObject])) {
		NSArray *components = [tag componentsSeparatedByString:@" "];
		NSEnumerator *tagEm = [components objectEnumerator];
		NSString *splitTag = nil;
		
		while ((splitTag = [tagEm nextObject])) {
			if (splitTag && [splitTag length]) {
				[allTags addObject:splitTag];
			}
		}
	}
	NSXMLElement *mediaKeywords = [NSXMLNode elementWithName:@"media:keywords"];
	NSString *keywords = [allTags componentsJoinedByString:@", "];
	[mediaKeywords setStringValue:keywords];
	[mediaGroup addChild:mediaKeywords];
	
	if (privacy == kOYPrivatePrivacy) {
		[mediaGroup addChild:[NSXMLNode elementWithName:@"yt:private"]];
	}

	NSMutableString *metaData = [[[NSMutableString alloc] initWithString:@"<?xml version='1.0' encoding='UTF-8'?>\n"] autorelease];
	[metaData appendString:[metaDataDoc XMLString]];
	
	[form setMetaData:metaData];
	[form setFile:fileName withContentType:kOYVideoContentType];
	formData = [form encodeForm];
	
	// generate the headers
	NSDictionary *headers = [NSDictionary dictionaryWithObjectsAndKeys:
							 [kOYAuthHeaderPrefix stringByAppendingString:[_authenticator token]], @"Authorization",
							 [_authenticator clientID], @"X-GData-Client",
							 [kOYDeveloperKeyPrefix stringByAppendingString:[_authenticator developerKey]], @"X-GData-Key",
							 [fileName lastPathComponent], @"Slug",
							 [form contentTypeHeaderValue], @"Content-Type",
							 [NSNumber numberWithUnsignedInt:[formData length]], @"Content-Length",
							 nil];
	
	// ship it!
	_uploadRequest = [[OYPOSTRequest alloc] initWithDelegate:self timeoutInterval:kOYTimeOutInterval];
	[_uploadRequest POSTRequest:uploadURL data:formData headers:headers userInfo:nil];	
}

- (void)cancelUpload {
	if (_uploadRequest) {
		[_uploadRequest cancel];
		[_uploadRequest autorelease];
		_uploadRequest = nil;
	}
}

- (BOOL)isUploading {
    return _uploadRequest != nil;
}

- (OYAuthenticator *)authenticator {
	return _authenticator;
}

- (void)setAuthenticator:(OYAuthenticator *)authenticator {
	[authenticator retain];
	[_authenticator release];
	_authenticator = authenticator;
}

#pragma mark -
#pragma mark Callbacks
#pragma mark -

// Upload POST callbacks
- (void)POSTRequest:(OYPOSTRequest *)request didComplete:(NSData *)data userInfo:(id)userinfo {
	NSError *error;
	NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithData:data options:0 error:&error] autorelease];
	
	if (error) {
		// If the response didn't parse, then whatever we got back wasn't a success state
		NSString *errorMessage = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
		NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errorMessage, NSLocalizedDescriptionKey, nil];
		NSError *errorWithMessage = [[[NSError alloc] initWithDomain:kOYResponseErrorDomain
																code:kOYResponseErrorCode 
															userInfo:userInfo] autorelease];
		[self _delegateError:errorWithMessage];
	} else  {
		OYVideoResponse *response = [[[OYVideoResponse alloc] initWithXMLDocument:doc] autorelease];
		if (response) {
			if (_delegate && [_delegate respondsToSelector:@selector(uploader:succededWithResponse:contextInfo:)]) {
				[_delegate uploader:self succededWithResponse:response contextInfo:_contextInfo];
			}
		} else {
			NSString *errorMessage = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
			NSLog(@"Error Response Text: %@", errorMessage);

			[self _delegateError:kOYResponseIDNotFoundErrorDomain
						withCode:kOYResponseIDNotFoundErrorCode];
		}
	}
	
	[self _cleanupUpload];
}

- (void)POSTRequest:(OYPOSTRequest *)request error:(NSError *)error userInfo:(id)userinfo {
	[self _delegateError:error];
	[self _cleanupUpload];
}

- (void)POSTRequest:(OYPOSTRequest *)request progress:(size_t)bytesSent total:(size_t)totalLength userInfo:(id)userinfo {
	if (_delegate && [_delegate respondsToSelector:@selector(uploader:progress:total:contextInfo:)]) {
		[_delegate uploader:self progress:bytesSent total:totalLength contextInfo:_contextInfo];
	}
}

- (void)POSTRequest:(OYPOSTRequest *)request didCancel:(id)userinfo {	
	[self _cleanupUpload];
}

- (void)POSTRequest:(OYPOSTRequest *)request didTimeout:(id)userinfo {
	[self _delegateError:kOYTimeOutErrorDomain withCode:kOYTimeOutErrorCode];
	[self _cleanupUpload];
}
@end

@implementation OYUploader (Private)
- (void)_delegateError:(NSString *)domain withCode:(int)code {
	if (_delegate && [_delegate respondsToSelector:@selector(uploader:failedUploadWithError:contextInfo:)]) {
		NSError *error = [NSError errorWithDomain:domain
											 code:code
										 userInfo:nil];
		[_delegate uploader:self failedUploadWithError:error contextInfo:_contextInfo];
	}
}

- (void)_delegateError:(NSError *)error {
	if (_delegate && [_delegate respondsToSelector:@selector(uploader:failedUploadWithError:contextInfo:)]) {
		[_delegate uploader:self failedUploadWithError:error contextInfo:_contextInfo];
	}
}

- (void)_cleanupUpload {
	[_uploadRequest autorelease];
	_uploadRequest = nil;
	_contextInfo = NULL;
}
@end